package com.jprocms.module.cms.service.channel;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.jpro.framework.common.util.collection.CollectionUtils;
import com.jpro.framework.common.util.object.BeanUtils;
import com.jpro.framework.datapermission.core.annotation.DataPermission;
import com.jpro.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.jprocms.module.cms.controller.admin.channel.dto.*;
import com.jprocms.module.cms.controller.admin.channel.vo.*;
import com.jprocms.module.cms.convert.PageConverter;
import com.jprocms.module.cms.convert.channel.ChannelAttrConvert;
import com.jprocms.module.cms.convert.channel.ChannelConvert;
import com.jprocms.module.cms.convert.channel.ChannelExtConvert;
import com.jprocms.module.cms.convert.channel.ChannelRuleMapConvert;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelAttrDO;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelExtDO;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelRuleMapDO;
import com.jprocms.module.cms.dal.dataobject.content.ContentDO;
import com.jprocms.module.cms.dal.dataobject.model.ModelDO;
import com.jprocms.module.cms.dal.mysql.channel.ChannelAttrMapper;
import com.jprocms.module.cms.dal.mysql.channel.ChannelExtMapper;
import com.jprocms.module.cms.dal.mysql.channel.ChannelMapper;
import com.jprocms.module.cms.dal.mysql.channel.ChannelRuleMapMapper;
import com.jprocms.module.cms.dal.mysql.content.ContentMapper;
import com.jprocms.module.cms.dal.mysql.model.ModelMapper;
import com.jprocms.module.cms.enums.EnumIntCommonStatus;
import com.jprocms.module.cms.enums.EnumRecycle;
import com.jprocms.module.cms.framework.tree.TreePathUtil;
import com.jprocms.module.cms.framework.tree.TreeServiceImpl;
import com.jprocms.module.cms.framework.util.SpringUtil;
import com.jprocms.module.cms.mq.message.bpm.BpmDeployMessage;
import com.jprocms.module.cms.permission.ChannelPermissionRule;
import com.jprocms.module.cms.permission.ContentPermissionRule;
import com.jprocms.module.cms.service.content.ContentService;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jprocms.module.cms.config.ThreadPoolConfiguration.CMS_COMMON_THREAD_POOL_TASK_EXECUTOR;
import static com.jprocms.module.cms.constant.CmsSysConstants.STATUS_CLOSE;
import static com.jprocms.module.cms.constant.CmsSysConstants.STATUS_OPEN;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;
import static com.jprocms.module.cms.enums.LogRecordConstants.*;

/**
 * 栏目 Service 实现类
 *
 * @author jprocms
 */
@Slf4j
@Service
@Validated
public class ChannelServiceImpl extends TreeServiceImpl<ChannelMapper, ChannelDO> implements ChannelService {

    @Resource
    private ChannelMapper channelMapper;
    @Resource
    private ChannelExtMapper channelExtMapper;
    @Resource
    private ContentMapper contentMapper;
    @Resource
    private PageConverter pageConverter;
    @Resource
    private ChannelAttrMapper channelAttrMapper;

    @Resource
    private ModelMapper modelMapper;

    @Resource
    private ChannelRuleMapMapper channelRuleMapMapper;
    @Lazy
    @Resource
    private ContentService contentService;
    @Resource(name = CMS_COMMON_THREAD_POOL_TASK_EXECUTOR)
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CMS_CHANNEL_TYPE, subType = CMS_CHANNEL_CREATE_SUB_TYPE, bizNo = "{{#channel.id}}", success = CMS_CHANNEL_CREATE_SUCCESS)
    public Long createChannel(ChannelCreateReqVO createReqVO) {
        Long siteId = CmsContextUtil.getCurrSiteId();
        checkModelId(createReqVO.getModelId(), siteId);
        checkParentChannelAndCloseMapRule(createReqVO.getParentId());
        //父栏目有内容创建失败
        if (ObjectUtils.isNotEmpty(createReqVO.getParentId()) && checkContentByChannelId(createReqVO.getParentId())) {
            throw exception(PARENT_CHANNEL_EXIST_CONTENT);
        }
        return doSaveChannel(createReqVO);
    }

    private Long doSaveChannel(ChannelCreateReqVO createReqVO) {
        Long currSiteId = CmsContextUtil.getCurrSiteId();
        //路径是否重复
        Boolean channelPathExist = checkPathExist(currSiteId, createReqVO.getPath(), null);
        // 插入
        ChannelDO channel = ChannelConvert.INSTANCE.convert(createReqVO);
        channel.setSiteId(currSiteId);
        channel.init();
        //nodePath 父类统一处理
        super.save(channel);
        ChannelExtDO extDO = ChannelExtConvert.INSTANCE.removeSuffixConvert(createReqVO);
        extDO.setId(channel.getId());
        extDO.init();
        channelExtMapper.insert(extDO);

        //自定义属性
        Map<String, Object> customs = createReqVO.getCustoms();
        saveCustoms(channel, customs);
        //路径重复了 修改路径加上id
        if (channelPathExist) {
            String path = generateChannelPath(channel.getPath(), channel.getId(), currSiteId);
            LambdaUpdateWrapper<ChannelDO> wrapper = new LambdaUpdateWrapper<>();
            wrapper.set(ChannelDO::getPath, path);
            wrapper.eq(ChannelDO::getId, channel.getId());
            super.update(new ChannelDO(), wrapper);
        }
        // 记录操作日志上下文
        LogRecordContext.putVariable("channel", channel);
        // 返回
        return channel.getId();
    }

    private String generateChannelPath(String path, Long channelId, Long currSiteId) {
        Assert.notNull(path, "path不能为空");
        Assert.notNull(path, "channelId不能为空");
        String genPath = path + RandomUtil.randomString(4);
        if (checkPathExist(currSiteId, path, channelId)) {
            return generateChannelPath(path, channelId, currSiteId);
        }
        return genPath;
    }


    @Override
    @LogRecord(type = CMS_CHANNEL_TYPE, subType = CMS_CHANNEL_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CMS_CHANNEL_UPDATE_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void updateChannel(ChannelUpdateReqVO updateReqVO) {
        // 校验存在
        Long currSiteId = CmsContextUtil.getCurrSiteId();
        validateChannelExists(updateReqVO.getId(), currSiteId);
        //路径是否重复
        Boolean channelPathExist = checkPathExist(currSiteId, updateReqVO.getPath(), updateReqVO.getId());
        Long channelId = updateReqVO.getId();
        //检查栏目状态
        ChannelDO channel = checkChannelId(channelId, Boolean.FALSE);
        //检查父栏目
        ChannelDO parentChannel = checkParentChannelAndCloseMapRule(updateReqVO.getParentId());
        //父栏目不能是子层级栏目,造成父子无限循环
        if (parentChannel != null) {
            final List<Long> parentIds = TreePathUtil.getParentIdsByPath(parentChannel.getNodePath());
            if (parentIds.contains(updateReqVO.getId())) {
                throw exception(PARENT_CHANNEL_ERROR);
            }
        }
        //检查是否当前站点栏目
        checkSite(currSiteId, channel.getSiteId());
        // 更新
        ChannelDO channelDO = ChannelConvert.INSTANCE.convert(updateReqVO);
        //调用父类重载方法更新nodepath
        updateById(channelDO);
        LambdaUpdateWrapper<ChannelDO> updateWrapper = new LambdaUpdateWrapper<>();
        //工作流设置可能为空
        if (StringUtils.isEmpty(updateReqVO.getProcessDefinitionId())) {
            updateWrapper.set(ChannelDO::getProcessDefinitionId, null);
        }
        //父栏目id可能为空
        if (com.jpro.framework.common.util.object.ObjectUtils.nullSafeEquals(channel.getParentId(), updateReqVO.getParentId())) {
            updateWrapper.set(ChannelDO::getParentId, updateReqVO.getParentId());
        }
        updateWrapper.eq(ChannelDO::getId, channel.getId());
        super.update(new ChannelDO(), updateWrapper);
        //更新ext表
        ChannelExtDO extDO = ChannelExtConvert.INSTANCE.removeSuffixConvert(updateReqVO);
        extDO.setId(channel.getId());
        channelExtMapper.updateById(extDO);
        //刪除原有 重新保存
        LambdaQueryWrapper<ChannelAttrDO> channelAttrQueryWrapper = Wrappers.lambdaQuery(ChannelAttrDO.class);
        channelAttrQueryWrapper.eq(ChannelAttrDO::getChannelId, updateReqVO.getId());
        channelAttrMapper.delete(channelAttrQueryWrapper);
        //自定义属性
        Map<String, Object> customs = updateReqVO.getCustoms();
        saveCustoms(channelDO, customs);
        //路径重复了 修改路径加上id
        if (channelPathExist) {
            String path = generateChannelPath(channel.getPath(), channel.getId(), currSiteId);
            LambdaUpdateWrapper<ChannelDO> updateWrapper2 = new LambdaUpdateWrapper<>();
            updateWrapper2.set(ChannelDO::getPath, path);
            updateWrapper2.eq(ChannelDO::getId, channel.getId());
            super.update(new ChannelDO(), updateWrapper2);
        }
        // 3. 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(channel, ChannelUpdateReqVO.class, item -> {
            item.setCommentStatus(channel.getChannelExt().getCommentStatus());
            item.setOutLinkUrl(channel.getChannelExt().getOutLinkUrl());
            item.setLinkTarget(channel.getChannelExt().getLinkTarget());
            item.setTplPc(channel.getChannelExt().getTplPc());
            item.setTplMobile(channel.getChannelExt().getTplMobile());
            item.setContentTplPc(channel.getChannelExt().getContentTplPc());
            item.setContentTplMobile(channel.getChannelExt().getContentTplMobile());
            item.setSeoKeyword(channel.getChannelExt().getSeoKeyword());
            item.setSeoTitle(channel.getChannelExt().getSeoTitle());
            item.setSeoDescription(channel.getChannelExt().getSeoDescription());
            item.setPageSize(channel.getChannelExt().getPageSize());
            item.setListPage(channel.getChannelExt().getListPage());
            item.setListImage(channel.getChannelExt().getListImage());
            item.setTxt(channel.getChannelExt().getTxt());
        }));
        LogRecordContext.putVariable("channel", channelDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CMS_CHANNEL_TYPE, subType = CMS_CHANNEL_UPDATE_SUB_TYPE, bizNo = "{{#ruleDto.id}}", success = CMS_CHANNEL_UPDATE_SUCCESS)
    public void updateChannelRule(ChannelRuleDto ruleDto) {
        Long channelId = ruleDto.getChannelId();
        ChannelExtDO extDO = channelExtMapper.selectById(channelId);
        Assert.notNull(extDO, String.format("未找到该栏目{}", channelId));
        final ChannelDO channel = getChannel(ruleDto.getChannelId());
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(channel, ChannelUpdateReqVO.class));
        LogRecordContext.putVariable("channel", channel);
        extDO.setCollectRule(ruleDto);
        channelExtMapper.updateById(extDO);
        //将栏目解析出来方便后面查询
        channelRuleMapMapper.deleteByTargetChannelId(channelId);
        List<ChannelRuleSourceDto> sourceChannels = ruleDto.getSourceChannels();
        List<ChannelRuleMapDO> ruleMapDOS = ChannelRuleMapConvert.INSTANCE.convertList(sourceChannels, channelId);
        channelRuleMapMapper.insertBatch(ruleMapDOS);
    }

    private void saveCustoms(ChannelDO channel, Map<String, Object> customs) {
        if (!customs.isEmpty()) {
            ModelDO modelDO = modelMapper.selectById(channel.getModelId());
            List<ChannelAttrDO> customDos = ChannelAttrConvert.INSTANCE.disassembleCustoms(modelDO, channel.getId(), customs);
            channelAttrMapper.insertBatch(customDos);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CMS_CHANNEL_TYPE, subType = CMS_CHANNEL_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CHANNEL_DELETE_SUCCESS)
    public void deleteChannel(Long id) throws Exception {
        // 校验存在
        ChannelDO channel = validateChannelExists(id, CmsContextUtil.getCurrSiteId());
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, channel);
        LogRecordContext.putVariable("channel", channel);
        // 删除
        List<? extends Serializable> channelIds = super.findChildrenIdById(id, true);
        for (Serializable cid : channelIds) {
            channelAttrMapper.delete(new LambdaQueryWrapperX<ChannelAttrDO>().eqIfPresent(ChannelAttrDO::getChannelId, cid));
        }
        channelMapper.deleteBatchIds(channelIds);
        channelExtMapper.deleteBatchIds(channelIds);
        //同步删除内容，如果需要保留内容，最好是先设计把内容还原的时候可以选择存在的栏目，TODO 后续考虑是否迭代支持？
        for (Serializable channelId : channelIds) {
            SpringUtil.getBean(ContentService.class).deleteContentsByChannelId((Long) channelId, false);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CMS_CHANNEL_TYPE, subType = CMS_CHANNEL_TORECYCLE_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CHANNEL_TORECYCLE_SUCCESS)
    public void recycleChannel(Long id) {
        // 校验存在
        validateChannelExists(id, CmsContextUtil.getCurrSiteId());
        List<ChannelDO> channels = super.findChildrenById(id, true);
        for (ChannelDO channel : channels) {
            channel.setRecycleStatus(true);
            LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, channel);
            LogRecordContext.putVariable("channel", channel);
        }
        channelMapper.updateBatch(channels, channels.size());
        //删除内容到回收站
        for (ChannelDO channelDO : channels) {
            SpringUtil.getBean(ContentService.class).deleteContentsByChannelId(channelDO.getId(), true);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CMS_CHANNEL_TYPE, subType = CMS_CHANNEL_RESTORE_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CHANNEL_RESTORE_SUCCESS)
    public void restoreChannel(Long id) {
        ChannelDO channelDO = validateChannelExists(id, CmsContextUtil.getCurrSiteId());
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, channelDO);
        LogRecordContext.putVariable("channel", channelDO);
        List<ChannelDO> channels = super.findChildrenById(id, true);
        for (ChannelDO channel : channels) {
            channel.setRecycleStatus(false);
        }
        channelMapper.updateBatch(channels, channels.size());
    }

    @Override
    @DataPermission(includeRules = {ChannelPermissionRule.class})
    @Transactional(rollbackFor = Exception.class)
    public void updateChannelWorkflowDefinitionId(BpmDeployMessage bpmDeployMessage) {
        String oldDefinitionId = bpmDeployMessage.getOldDefinitionId();
        List<ChannelDO> channelDOS = channelMapper.selectListByProcessDefinitionId(oldDefinitionId);
        channelDOS.forEach(c -> c.setProcessDefinitionId(bpmDeployMessage.getDefinitionId()));
        saveOrUpdateBatch(channelDOS);
    }

    private ChannelDO validateChannelExists(Long id, Long siteId) {
        ChannelDO channelDO = channelMapper.selectById(id);
        if (channelDO == null) {
            throw exception(CHANNEL_NOT_EXISTS);
        }
        //验证是否当前站点栏目
        if (!channelDO.getSiteId().equals(siteId)) {
            throw exception(CHANNEL_NOT_EXISTS);
        }
        return channelDO;
    }

    @Override
    public ChannelDO getChannel(Long id) {
        return channelMapper.selectById(id);
    }

    @Override
    public List<ChannelDO> getChannelList(Collection<Long> ids) {
        return channelMapper.selectBatchIds(ids);
    }

    //栏目和内容类权限都是过滤栏目字段，这里是查询栏目过滤内容类规则
    @Override
    public List<ChannelDO> getChannelList(ChannelListReqVO listReqVO) {
        return channelMapper.selectChannelList(listReqVO);
    }

    @Override
    public List<ChannelDO> getChannelListForFront(ChannelListReqVO listReqVO) {
        return channelMapper.selectChannelListForFront(listReqVO);
    }

    @Override
    public List<ChannelDO> getNotAllowSearchChannelList(Long siteId) {
        ChannelListReqVO listReqVO = new ChannelListReqVO();
        listReqVO.setSiteId(siteId);
        listReqVO.setSearchStatus(EnumIntCommonStatus.CLOSE.getStatus());
        return getChannelListForFront(listReqVO);
    }

    @Override
    @DataPermission(includeRules = {ChannelPermissionRule.class})
    public List<ChannelDO> getChannelOptions(ChannelSimpleReqVO listReqVO) {
        return channelMapper.selectListForOptions(listReqVO);
    }

    @Override
    @DataPermission(includeRules = {ChannelPermissionRule.class})
    public List<ChannelListRespVO> getChannelsTreeVo(ChannelListReqVO listReqVO) {
        List<ChannelDO> list = getChannelList(listReqVO);
        return convertToRespVo(list);
    }

    @Override
    public List<ChannelViewListRespVO> getChannelsViewListVo(ChannelListReqVO listReqVO) {
        List<ChannelDO> list = getChannelList(listReqVO);
        if (list.isEmpty()) {
            return Collections.emptyList();
        }
        list.sort(Comparator.comparing(ChannelDO::getViews).reversed());
        List<ChannelViewListRespVO> channelListRespVOS = ChannelConvert.INSTANCE.convertViewListResp(list);
        return channelListRespVOS;
    }

    @Override
    @DataPermission(includeRules = {ContentPermissionRule.class})
    public List<ChannelListRespVO> getContentChannelsTreeVo(ContentChannelListReqVO listReqVO) {
        List<ChannelDO> list = channelMapper.selectContentChannelList(listReqVO);
        return convertToRespVo(list);
    }

    private List<ChannelListRespVO> convertToRespVo(List<ChannelDO> list) {
        if (list.isEmpty()) {
            return Collections.emptyList();
        }
        List<ModelDO> modelDOS = modelMapper.selectBatchIds(CollectionUtils.convertList(list, ChannelDO::getModelId));
        Map<Long, String> modelIdNameMap = CollectionUtils.convertMap(modelDOS, ModelDO::getId, ModelDO::getName);
        list.sort(Comparator.comparing(ChannelDO::getSort));
        List<ChannelListRespVO> channelListRespVOS = ChannelConvert.INSTANCE.convertListResp(list, modelIdNameMap);
        return channelListRespVOS;
    }

    @Override
    public List<ChannelDO> selectLeafChannels(Long channelId, String parentPath) {
        if (channelId == null) {
            return Collections.emptyList();
        }
        ChannelDO channel = null;
        if (StringUtils.isBlank(parentPath)) {
            channel = getById(channelId);
            if (channel == null || channel.getRecycle()) {
                return Collections.emptyList();
            }
            parentPath = channel.getPath();
        }
        LambdaQueryWrapper<ChannelDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChannelDO::getRecycleStatus, EnumRecycle.NO_RECYCLE.getStatus());
        queryWrapper.select(ChannelDO::getName, ChannelDO::getId, ChannelDO::getParentId);
        String path = TreePathUtil.generateChildPath(parentPath, channelId);
        List<ChannelDO> channels = listByPath(queryWrapper, path);
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(channels)) {
            return Lists.newArrayList(channel == null ? getById(channelId) : channel);
        }
        Map<Long, List<ChannelDO>> parentMap = channels.stream().collect(Collectors.groupingBy(ChannelDO::getParentId));
        return channels.stream().filter(item -> !parentMap.containsKey(item.getId())).collect(Collectors.toList());
    }

    @Override
    public List<ChannelDO> selectLeafChannels(Collection<Long> channelIds) {
        List<ChannelDO> channels = getChannelList(channelIds).stream().filter(item -> !item.getRecycle()).collect(Collectors.toList());
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(channels)) {
            return new ArrayList<>();
        }
        Set<Long> ids = com.jpro.framework.common.util.collection.CollectionUtils.convertSet(channels, ChannelDO::getId);
        List<ChannelDO> parentTrees = channels.stream().filter(c -> c.getParentId() == null || !ids.contains(c.getParentId())).collect(Collectors.toList());
        List<ChannelDO> leafChannels = parentTrees.stream().flatMap(item -> selectLeafChannels(item.getId(), item.getNodePath()).stream()).collect(Collectors.toList());
        return leafChannels;
    }

    @Override
    public List<ChannelDO> selectByChannelPath(Collection<String> paths, Long siteId) {
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(paths)) {
            return Collections.emptyList();
        }
        return channelMapper.selectByChannelPath(ChannelFrontListReqVO.of(siteId, paths, EnumRecycle.NO_RECYCLE.getStatus()));
    }

    @Override
    public ChannelDO selectByChannelPath(String path, Long siteId) {
        List<ChannelDO> channelDOS = selectByChannelPath(Arrays.asList(path), siteId);
        if (CollUtil.isNotEmpty(channelDOS)) {
            return channelDOS.get(0);
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean batchCreateChannels(ChannelBatchCreateDto dto, Long currSiteId) {
        Long parentId = dto.getParentId();
        checkParentChannelAndCloseMapRule(parentId);
        //父栏目有内容不允许创建子栏目
        if (ObjectUtils.isNotEmpty(parentId) && checkContentByChannelId(parentId)) {
            throw exception(PARENT_CHANNEL_EXIST_CONTENT);
        }
        List<ChannelCreateTreeNodeDto> addTreeNodes = batchAddDtoToChannelCreateTreeNodeDto(dto, currSiteId);
        batchCreateChannel(addTreeNodes, parentId, currSiteId);
        return Boolean.TRUE;
    }

    @Override
    @DataPermission(includeRules = {ChannelPermissionRule.class})
    @Transactional(rollbackFor = Exception.class)
    public Boolean copyChannels(Long siteId, Long newSiteId) {
        ChannelListReqVO listReqVO = new ChannelListReqVO();
        listReqVO.setSiteId(siteId);
        listReqVO.setRecycleStatus(EnumRecycle.NO_RECYCLE.getStatus());
        final List<ChannelDO> channelList = getChannelList(listReqVO);
        List<ChannelDO> newSiteChannelList = new ArrayList<>();
        for (ChannelDO channelDO : channelList) {
            newSiteChannelList.add(doCopyChannel(channelDO, newSiteId));
        }
        resetChannelsParent(newSiteChannelList);
        return true;
    }

    @Override
    @DataPermission(includeRules = {ChannelPermissionRule.class})
    @Transactional(rollbackFor = Exception.class)
    public Boolean copyChannelsAndContents(Long siteId, Long newSiteId) {
        ChannelListReqVO listReqVO = new ChannelListReqVO();
        listReqVO.setSiteId(siteId);
        listReqVO.setRecycleStatus(EnumRecycle.NO_RECYCLE.getStatus());
        final List<ChannelDO> channelList = getChannelList(listReqVO);
        //复制内容数据过多采用子线程运行
        threadPoolTaskExecutor.execute(
                () -> {
                    log.info("开始复制内容");
                    long t1 = System.currentTimeMillis();
                    List<ChannelDO> newSiteChannelList = new ArrayList<>();
                    for (ChannelDO channelDO : channelList) {
                        Long originChannelId = channelDO.getId();
                        channelDO = doCopyChannel(channelDO, newSiteId);
                        newSiteChannelList.add(channelDO);
                        Long newChannelId = channelDO.getId();
                        contentService.copyChannelContent(originChannelId, newChannelId, newSiteId);
                    }
                    resetChannelsParent(newSiteChannelList);
                    long t2 = System.currentTimeMillis();
                    log.info("结束复制内容 耗时{}", (t2 - t1));
                }
        );
        return true;
    }

    //将新复制的栏目重置为新创建的父栏目
    private void resetChannelsParent(List<ChannelDO> newSiteChannelList) {
        for (ChannelDO channelDO : newSiteChannelList) {
            final Long parentId = channelDO.getParentId();
            if (parentId != null) {
                //找到父栏目id去寻找父栏目路径然后在本站寻找对应的新父栏目设置上修改
                final ChannelDO oriParentChannel = channelMapper.selectById(parentId);
                final String parentPath = oriParentChannel.getPath();
                final ChannelDO parentChannel = selectByChannelPath(parentPath, channelDO.getSiteId());
                channelDO.setParentId(parentChannel.getId());
                super.updateById(channelDO);
            }
        }
    }

    private ChannelDO doCopyChannel(ChannelDO channelDO, Long newSiteId) {
        channelDO.setId(null);
        channelDO.setSiteId(newSiteId);
        channelMapper.insert(channelDO);
        Long channelId = channelDO.getId();

        ChannelExtDO extDO = channelDO.getChannelExt();
        extDO.setId(channelId);
        channelExtMapper.insert(extDO);

        List<ChannelAttrDO> attrDos = channelDO.getAttrDos();
        for (ChannelAttrDO attrDo : attrDos) {
            attrDo.setChannelId(channelId);
        }
        channelAttrMapper.insertBatch(attrDos);
        return channelDO;
    }

    private Boolean checkContentByChannelId(Long channelId) {
        if (channelId == null) {
            return false;
        }
        LambdaQueryWrapper<ContentDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ContentDO::getChannelId, channelId);
        queryWrapper.eq(ContentDO::getDeleted, 0);
        return SpringUtil.getBean(ContentService.class).exists(queryWrapper);
    }

    private void batchCreateChannel(List<ChannelCreateTreeNodeDto> addTreeNodeDtoList, Long parentId, Long currSiteId) {
        if (CollectionUtil.isNotEmpty(addTreeNodeDtoList)) {
            for (ChannelCreateTreeNodeDto addTreeNodeDto : addTreeNodeDtoList) {
                ChannelDO channel = addTreeNodeDto.getChannel();
                channel.setParentId(parentId);
                this.saveChannelSimple(channel, currSiteId);
                List<ChannelCreateTreeNodeDto> subTreeDtoList = addTreeNodeDto.getSubTreeDtoList();
                batchCreateChannel(subTreeDtoList, channel.getId(), currSiteId);
            }
        }
    }

    private Long saveChannelSimple(ChannelDO channel, Long siteId) {
        // 插入
        channel.setSiteId(siteId);
        channel.init();
        //nodePath 父类统一处理
        super.save(channel);
        ChannelExtDO extDO = new ChannelExtDO();
        extDO.setId(channel.getId());
        extDO.init();
        channelExtMapper.insert(extDO);
        // 返回
        return channel.getId();
    }

    private List<ChannelCreateTreeNodeDto> batchAddDtoToChannelCreateTreeNodeDto(ChannelBatchCreateDto batchAddDto, Long currSiteId) {
        List<ChannelCreateTreeNodeDto> addTreeNodeDtoList = new ArrayList<>();
        Long modelId = batchAddDto.getModelId();
        List<ChannelBatchCreateNameDto> nameTreeDtoList = batchAddDto.getChannelTree();
        converterToChannelCreateTreeNodeDtoList(nameTreeDtoList, addTreeNodeDtoList, modelId,
                currSiteId);
        return addTreeNodeDtoList;
    }

    private void converterToChannelCreateTreeNodeDtoList(List<ChannelBatchCreateNameDto> nameTreeDtoList,
                                                         List<ChannelCreateTreeNodeDto> addTreeNodeDtoList, Long modelId, Long currSiteId) {
        if (CollectionUtil.isNotEmpty(nameTreeDtoList)) {
            for (ChannelBatchCreateNameDto dto : nameTreeDtoList) {
                if (ObjectUtils.isNotEmpty(dto)) {
                    String channelName = dto.getChannelName();
                    if (StringUtils.isNotEmpty(channelName)) {
                        ChannelDO channel = ChannelConvert.INSTANCE.convertToChannel(channelName, modelId, currSiteId);
                        ChannelCreateTreeNodeDto addTreeNodeDto = new ChannelCreateTreeNodeDto();
                        addTreeNodeDto.setChannel(channel);
                        List<ChannelCreateTreeNodeDto> nodeDtoList = new ArrayList<>();
                        List<ChannelBatchCreateNameDto> children = dto.getChildren();
                        converterToChannelCreateTreeNodeDtoList(children, nodeDtoList, modelId,
                                currSiteId);
                        addTreeNodeDto.setSubTreeDtoList(nodeDtoList);
                        addTreeNodeDtoList.add(addTreeNodeDto);
                    }
                }
            }
        }
    }

    /**
     * 检查父级栏目id是否存在映射规则 父栏目存在规则则关闭映射规则
     *
     * @param parentChannelId
     */
    private ChannelDO checkParentChannelAndCloseMapRule(Long parentChannelId) {
        if (ObjectUtils.isNotEmpty(parentChannelId)) {
            ChannelDO parentChannel = checkChannelId(parentChannelId, Boolean.FALSE);
            //检查规则并重置关闭
            ChannelExtDO extDO = channelExtMapper.selectById(parentChannelId);
            if (ObjectUtils.isNotEmpty(extDO)) {
                ChannelRuleDto channelRuleDto = extDO.getCollectRule();
                if (ObjectUtils.isNotEmpty(channelRuleDto)) {
                    if (STATUS_OPEN.equals(channelRuleDto.getStatus())) {
                        channelRuleDto.setStatus(STATUS_CLOSE);
                    }
                }
            }
            return parentChannel;
        }
        return null;
    }

    /**
     * 检查栏目id是否合法
     *
     * @param channelId
     */
    private ChannelDO checkChannelId(Long channelId, Boolean recycled) {
        ChannelDO channel = super.getById(channelId);
        if (ObjectUtils.isEmpty(channel)) {
            throw exception(CHANNEL_NOT_EXISTS);
        }
        //不能是回收站的栏目
        if (recycled) {
            if (!channel.getRecycle()) {
                exception(COMMON_PARAM_ERROR);
            }
        } else {
            if (channel.getRecycle()) {
                exception(COMMON_PARAM_ERROR);
            }
        }
        return channel;
    }

    /**
     * 检查模型是否合法
     *
     * @param modelId
     */
    private void checkModelId(Long modelId, Long siteId) {
        ModelDO model = modelMapper.selectById(modelId);
        if (ObjectUtils.isEmpty(model)) {
            exception(COMMON_PARAM_ERROR);
        }
        if (!model.getGlobal()) {
            if (!siteId.equals(model.getSiteId())) {
                exception(COMMON_PARAM_ERROR);
            }
        }
    }

    /**
     * 根据siteId、channelPath查询记录是否存在
     * 修改的时候传递channelId 查询的包含这个id 说明这个路径可用 新增不传递channelId
     * 重复 getPath = getPath + 随机字符串
     *
     * @param siteId 当前站点ID
     * @param path   栏目目录
     * @return
     */
    private Boolean checkPathExist(Long siteId, String path, Long channelId) {
        LambdaQueryWrapper<ChannelDO> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ChannelDO::getPath, path);
        wrapper.eq(ChannelDO::getSiteId, siteId);
        List<ChannelDO> list = super.list(wrapper);
        if (list.isEmpty()) {
            return false;
        }
        if (channelId != null && CollectionUtils.convertList(list, ChannelDO::getId).contains(channelId)) {
            return false;
        }
        return true;
    }

    /**
     * 检查siteId
     *
     * @param siteId
     * @param channelSiteId
     */
    private void checkSite(Long siteId, Long channelSiteId) {
        if (!siteId.equals(channelSiteId)) {
            exception(COMMON_PARAM_ERROR);
        }
    }


}
